﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;

namespace CopyCameraFiles
{
    public partial class PreferencesForm : Form
    {
        const string asterisk = "*";
        private char[] splitChar = { ',' };
        private List<FileInfo> filesToCopy = new List<FileInfo>();
        private char[] invalidChars = { '/', '\\', ':', '?', '<', '>', '|' };
        private List<string> fileExtentionList;
        private BackgroundWorker searchWorker;
        private BackgroundWorker copyWorker;
        private FileFinder finder;
        private FileCopier copier;

        public PreferencesForm()
        {
            InitializeComponent();
            finder = new FileFinder();
            copier = new FileCopier();
            InitForm();
        }

        #region Properties

        public string NotificationMessage {
            set { 
                notifyIcon.ShowBalloonTip(1000, Application.ProductName, value, ToolTipIcon.Info);
            }
        }

        public SearchOption DirectorySearchOption { 
            get
            { 
                return (chkSearchAllDirectories.Checked) ? SearchOption.AllDirectories: SearchOption.TopDirectoryOnly;
            }
            set 
            {
                if (value == SearchOption.AllDirectories)
                    chkSearchAllDirectories.Checked = true;
                else
                    chkSearchAllDirectories.Checked = false;
            }
        }

        public string StatusMessage {
            get { return toolStripStatusLabel.Text; }
            set { toolStripStatusLabel.Text = value; }
        }

        public string SourcePath { 
            get { return cbSourcePath.Text; }
            set { cbSourcePath.Text = value; }
        }

        public string DestinationPath {
            get { return tbDestinationPath.Text; }
            set { tbDestinationPath.Text = value; }
        }

        public string StartingWith {
            get {
                if (string.IsNullOrEmpty(tbStartingWith.Text))
                    return asterisk;
                return tbStartingWith.Text;
            }
            set {
                tbStartingWith.Text = value;
                BuildFilePattern();
            }
        }

        public string EndingWith {
            get {
                if (string.IsNullOrEmpty(tbEndingWith.Text))
                    return asterisk;
                return tbEndingWith.Text;
            }
            set {                
                tbEndingWith.Text = value;
                BuildFilePattern();
            }
        }

        public string WithExtention {
            get {
                if (string.IsNullOrEmpty(cbWithExtention.Text))
                    return asterisk;    
                return cbWithExtention.Text;
            }
            set {
                cbWithExtention.SelectedItem = value;
                BuildFilePattern();
            }
        }

        private string Pattern
        {
            get { return BuildFilePattern(); }
            set { value = BuildFilePattern(); }
        }

        #endregion Properties
                
        #region Methods

        private void InitForm()
        {
            SetDefaultFont();
            LoadFormPosition();
            LoadFileExtentions();
            LoadUserPreferences();
            PopulateDrivesList();
            SetIcons();
            InitSearchWorker();
            this.Text = string.Format("{0}", Application.ProductName);
        }

        private void SetDefaultFont()
        {
            Font f = new Font("MS Sans Serif", 9f);
            this.Font = f;
        }

        private void PopulateDrivesList()
        {
            cbSourcePath.Items.AddRange(Directory.GetLogicalDrives());
        }

        private void SetIcons()
        {
            this.Icon = ResourceFile.CopyFiles;
            notifyIcon.Icon = ResourceFile.CopyFiles;
            this.notifyIcon.Visible = true;
        }

        private void InitCopyWorker()
        {
            copyWorker = new BackgroundWorker();
            copyWorker.WorkerReportsProgress = true;
            copyWorker.WorkerSupportsCancellation = true;
            copyWorker.DoWork += new DoWorkEventHandler(copyWorker_DoWork);
            copyWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(copyWorker_RunWorkerCompleted);
        }

        private void InitSearchWorker()
        {
            searchWorker = new BackgroundWorker();
            searchWorker.WorkerReportsProgress = false;
            searchWorker.WorkerSupportsCancellation = true;
            searchWorker.DoWork += new DoWorkEventHandler(searchWorker_DoWork);
            searchWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(searchWorker_RunWorkerCompleted);
        }

        private void LoadFormPosition()
        {
            this.StartPosition = FormStartPosition.Manual;
            this.Location = FormSettings.Default.FormLocation;
            this.Size = FormSettings.Default.FormSize;
            this.WindowState = FormSettings.Default.FormWindowState;
        }

        private void LoadFileExtentions()
        {
            fileExtentionList = new List<string>();
            fileExtentionList = SettingsFile.Default.FileExtentions.Split(splitChar, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).OrderBy(s => s).ToList();
            cbWithExtention.Items.AddRange(fileExtentionList.ToArray());
        }
        
        private string BuildFilePattern()
        {
            if (StartingWith == asterisk && EndingWith == asterisk)
                return string.Format("{0}.{1}", StartingWith, WithExtention);
            return string.Format("{0}{1}.{2}", StartingWith, EndingWith, WithExtention);
        }

        private string ShowFolderBrowseDialog(string defaultPath)
        {
            var dialog = new FolderBrowserDialog();
            //TODO: determine if defaultPath is a root folder (e.g. C:\)
            if (Directory.Exists(defaultPath)) dialog.SelectedPath = defaultPath;
            var dialogResult = dialog.ShowDialog();
            if (dialogResult == DialogResult.OK)
            {
                return dialog.SelectedPath;
            }
            return null;
        }
        
        private void LoadUserPreferences()
        {
            SettingsFile.Default.SettingsLoaded += new System.Configuration.SettingsLoadedEventHandler(Default_SettingsLoaded);
            SettingsFile.Default.SettingsSaving += new System.Configuration.SettingsSavingEventHandler(Default_SettingsSaving);
            DestinationPath = SettingsFile.Default.DestinationPath;
            SourcePath = SettingsFile.Default.SourcePath;
            StartingWith = SettingsFile.Default.FileStartsWith;
            EndingWith = SettingsFile.Default.FileEndsWith;
            WithExtention = SettingsFile.Default.WithExtention;
            DirectorySearchOption = (SettingsFile.Default.IncludeSubFolders) ? SearchOption.AllDirectories: SearchOption.TopDirectoryOnly;
            UpdateStatus("Preferences loaded.");
        }

        private void SaveUserPreferences()
        {
            SettingsFile.Default.DestinationPath = DestinationPath;
            SettingsFile.Default.SourcePath = SourcePath;
            SettingsFile.Default.FileStartsWith = StartingWith;
            SettingsFile.Default.FileEndsWith = EndingWith;
            SettingsFile.Default.WithExtention = WithExtention;
            SettingsFile.Default.IncludeSubFolders = (DirectorySearchOption == SearchOption.AllDirectories) ? true : false;
            SettingsFile.Default.Save();
        }

        private void ShowNrOfFilesFound(int nrOfFilesFound)
        {
            string message = null;
            if (nrOfFilesFound == 0)
                message = "No files have been found.";
            else if (nrOfFilesFound == 1)
                message = "Found 1 file.";
            else
                message = string.Format("Found {0} files.", nrOfFilesFound);
            StatusMessage = message;
            NotificationMessage = message;
        }

        /// <summary>
        /// Verifies if the given string contains any of the invalid 
        /// characters as specified in the invalidChars array.
        /// </summary>
        /// <param name="pattern">string to verify</param>
        /// <returns>true if the string is valid, false if the string contains invalid characters.</returns>
        private bool ValidateCharacters(string pattern)
        {
            if (pattern.IndexOfAny(invalidChars) >= 0)
                return false;
            return true;
        }

        private void FindAndCopyFiles()
        {
            // if cancellation is in progess return
            if (copyWorker.CancellationPending) return;

            // if copying is in progress cancel the process
            if (copyWorker.IsBusy)
            {
                //TODO: do stuff to cancel copying
                return;
            }

            // execute search and copy
            // if list with files is empty perform search
            if (finder.FileCount == 0)
            {
                StartFileSearch();
            } 
            //if no files are found exit
            if (finder.FileCount == 0) return;
            
            //execute copy
            StatusMessage = "Copying files...";
            copyWorker.RunWorkerAsync();
        }

        /// <summary>
        /// Saves form location, size and windowstate in the FormSettings file.
        /// </summary>
        private void SaveFormPosition()
        {
            if (this.WindowState == FormWindowState.Normal)
            {
                FormSettings.Default.FormLocation = this.Location;
                FormSettings.Default.FormSize = this.Size;
            }
            else
            {
                FormSettings.Default.FormLocation = this.RestoreBounds.Location;
                FormSettings.Default.FormSize = this.RestoreBounds.Size;
            }
            FormSettings.Default.FormWindowState = this.WindowState;
            FormSettings.Default.Save();
        }

        private void UpdateStatus(string message)
        {
            Debug.Assert(this.InvokeRequired == false);
            if (this.InvokeRequired) throw new Exception("threading issue");
            StatusMessage = message;
            toolStripStatusLabel.Invalidate();
            statusStrip.Update();
        }

        private void ExitApp()
        {
            if (this.ValidateChildren())
                SaveUserPreferences();
            Application.Exit();
        }

        /// <summary>
        /// Disables buttons during a file search of copy action
        /// </summary>
        private void DisableButtons()
        {
            btnSource.Enabled = false;
            btnDestination.Enabled = false;
            btnCopy.Enabled = false;
        }

        #endregion Methods

        #region Events

        private void Default_SettingsSaving(object sender, CancelEventArgs e)
        {
            UpdateStatus("Preferences saved.");
        }

        private void Default_SettingsLoaded(object sender, System.Configuration.SettingsLoadedEventArgs e)
        {
            UpdateStatus("Preferences loaded.");
        }

        private void OnPatternChange(object sender, EventArgs e)
        {
            lblMatch.Text = Pattern;
        }

        /// <summary>
        /// This event either starts the file search or cancels it.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSource_Click(object sender, EventArgs e)
        {
            StartFileSearch();
        }

        private void StartFileSearch()
        {
            // do not continue if cancellation is already in progress
            if (searchWorker.CancellationPending) return;

            // if worker is executing, cancel it
            if (searchWorker.IsBusy)
            {
                DisableButtons();
                StatusMessage = "Cancelling search...";
                Cursor.Current = Cursors.WaitCursor;
                searchWorker.CancelAsync();
                return;
            }

            //execute the file search
            var result = ShowFolderBrowseDialog(SourcePath);
            if (string.IsNullOrEmpty(result))
            {
                StatusMessage = "User cancelled, no source selected.";
                return;
            }

            ShowSearchCancelButton();
            SourcePath = result;
            InitFinder();

            // search for files asynchronously
            StatusMessage = "Searching...";
            Cursor.Current = Cursors.WaitCursor; //TODO: change in cursor is not visible
            searchWorker.RunWorkerAsync(); 
        }

        private void InitFinder()
        {
            finder.Path = SourcePath;
            finder.Pattern = Pattern;
            finder.DirectorySearchOption = DirectorySearchOption;
            finder.ClearFileList();
        }

        private void ResetSearchButton()
        {
            btnSource.Text = "...";
            btnSource.Enabled = true;
        }

        private void ShowSearchCancelButton()
        {
            btnSource.Text = "X";
        }

        private void searchWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            finder.StartFindFiles(searchWorker, finder.Path);

            //check if user cancelled
            if (searchWorker.CancellationPending)
                e.Cancel = true;
        }

        private void searchWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //check if there was an error, ifo so show it ot the user
            if (e.Error != null)
            {
                ShowError(e);
                return;
            }

            if (e.Cancelled)
            {
                UpdateStatus("Search cancelled.");
                ResetSearchButton();
                return;
            }

            // no error, show nr of files that have been found
            ResetSearchButton();
            var nrOfFiles = finder.FileCount;
            ShowNrOfFilesFound(nrOfFiles);
            Cursor.Current = this.Cursor;
        }

        private void copyWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            DirectoryInfo di = new DirectoryInfo(DestinationPath);
            var filesCopied = copier.Copy(filesToCopy, di);
            UpdateStatus(string.Format("{0} files have been copied.", filesCopied));

            //check if user cancelled
            if (copyWorker.CancellationPending)
                e.Cancel = true;
        }

        private void copyWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //check if there was an error
            if (e.Error != null)
            {
                ShowError(e);
                return;
            }

            if (e.Cancelled)
            {
                UpdateStatus("Copying cancelled");
                ResetCopyButton();
                return;
            }

            // no error
            UpdateStatus("Finished");
            Cursor.Current = this.Cursor;
        }

        private void ResetCopyButton()
        { 
        }

        private void ShowError(RunWorkerCompletedEventArgs e)
        {
            StatusMessage = e.Error.Message;
            NotificationMessage = e.Error.Message; ;
        }
       
        private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // not used any more
            UpdateStatus(string.Format("{0} files found...",(int)e.ProgressPercentage));
        }     

        private void btnDestination_Click(object sender, EventArgs e)
        {
            var result = ShowFolderBrowseDialog(DestinationPath);
            if (string.IsNullOrEmpty(result))
            {
                StatusMessage = "User cancelled, no destination selected.";
                return;
            }
            DestinationPath = result;
        }

        private void btnCopy_Click(object sender, EventArgs e)
        {
            if (!this.ValidateChildren())
            {
                return;
            }
            SaveUserPreferences();
            FindAndCopyFiles();
        }

        private void Path_Validating(object sender, CancelEventArgs e)
        {
            string errorMessage = null;
            var path = ((Control)sender).Text.Trim();
            if (string.IsNullOrEmpty(path) || !Directory.Exists(path))
            {
                errorMessage = "Please specify a valid path.";
                e.Cancel = true;
            }
            folderErrorProvider.SetError((Control)sender, errorMessage);
            if (!string.IsNullOrEmpty(errorMessage)) StatusMessage = errorMessage;
        }

        private void FileMatch_Validating(object sender, CancelEventArgs e)
        {
            string errorMessage = null;
            var pattern = ((Control)sender).Text.Trim();
            if (!ValidateCharacters(pattern))
            {
                errorMessage = "Invalid character used: " + new string(invalidChars);
                e.Cancel = true;
            }
            fileMatchErrorProvider.SetError((Control)sender, errorMessage);
            if (!string.IsNullOrEmpty(errorMessage)) StatusMessage = errorMessage;
        }

        private void PreferencesForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            SaveFormPosition();
        } 

        private void PreferencesForm_Resize(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Minimized)
            {
                Hide();
            }
        }

        private void notifyIcon_DoubleClick(object sender, EventArgs e)
        {
            this.Show();
            this.WindowState = FormWindowState.Normal;
        }
        
        private void preferencesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Show();
            this.WindowState = FormWindowState.Normal;
        }

        private void findCopyFilesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            FindAndCopyFiles();
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ExitApp();
        }

        private void savePreferencesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (!this.ValidateChildren())
            {
                return;
            }
            SaveUserPreferences();
        }

        private void toolStripMenuItem2_Click(object sender, EventArgs e)
        {
            ExitApp();
        }

        private void loadPreferencesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            LoadUserPreferences();
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var aboutBox = new AboutBox() { Owner = this };
            aboutBox.ShowDialog();
        }

        private void helpToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Bummer...this feature has not been implemented yet.","Help", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        }

        #endregion Events
    }
}
